﻿//-----------------------------------------------------------------------
// <copyright file="MLAsyncWorker.cs" company="MAPILab Ltd">
//     Copyright (c) MAPILab Ltd.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace MAPILab.SharePoint.Explorer.ObjectBrowser.AsyncWork
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Forms;
    using MAPILab.SharePoint.Explorer.ObjectBrowser;
    using MAPILab.SharePoint.Explorer.ObjectBrowser.TreeExpand;

    /// <summary>
    /// Class for async tree expand
    /// </summary>
    public class MLAsyncWorker : IDisposable
    {
        #region Fields

        /// <summary>
        /// Number of background workers
        /// </summary>
        private static int numberOfBackgroundWorkers = Properties.Settings.Default.NumberOfThreads;

        /// <summary>
        /// Threads to abort process
        /// </summary>
        private Thread[] mThread;

        /// <summary>
        /// Background workers for expand tree in assync mode
        /// </summary>
        private BackgroundWorker[] backgroundWorker;

        /// <summary>
        /// Three collections for background workers
        /// </summary>
        private List<MLNodeInfo>[] treeNodeCollection;

        /// <summary>
        /// Parent object browser
        /// </summary>
        public MLObjectBrowser objectBrowser;

        #endregion

        #region Events

        /// <summary>
        /// Work for background worker
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WorkForBackgroundWorkers(object sender, DoWorkEventArgs e)
        {
            if (e.Argument is MLDoWork)
            {
                ProcessAsyncWork(e.Argument as MLDoWork);
            }
        }

        /// <summary>
        /// Report progress from background worker
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ReportProgressOfBackgroundWorker(object sender, ProgressChangedEventArgs e)
        {
            if (e.UserState is MLDoWork)
            {
                BuildTree(e.UserState as MLDoWork);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Add work to free background worker
        /// </summary>
        /// <param name="work">Work to do</param>
        /// <returns>Is added or not</returns>
        public bool AddWork(MLDoWork work)
        {
            for (int i = 0; i < numberOfBackgroundWorkers; i++)
            {
                if (!backgroundWorker[i].IsBusy)
                {
                    objectBrowser.IsRunning = true;
                    work.BackgroundWorkerNumber = i;
                    backgroundWorker[i].RunWorkerAsync(work);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Process work in async mode
        /// </summary>
        /// <param name="currentWork">Work to process</param>
        private void ProcessAsyncWork(MLDoWork currentWork)
        {
            mThread[currentWork.BackgroundWorkerNumber] = Thread.CurrentThread;
            try
            {
                if (currentWork.WorkState == WorkState.expand)
                {
                    ProcessChild(currentWork);
                }
                else if (currentWork.WorkState == WorkState.start)
                {
                    ProcessRoot(currentWork);
                }
            }
            catch (ThreadAbortException)
            {
                Thread.ResetAbort();
                backgroundWorker[currentWork.BackgroundWorkerNumber].ReportProgress(0, new MLDoWork(WorkState.aborted, currentWork.Node, null, currentWork.BackgroundWorkerNumber));
            }
            finally
            {
                mThread[currentWork.BackgroundWorkerNumber] = null;
            }
        }

        /// <summary>
        /// Adding properties, methods and etc.
        /// </summary>
        /// <param name="progressWork">Work to process</param>
        private void ProcessChild(MLDoWork progressWork)
        {
            if (progressWork.Node.Tag != null)
            {
                if (!(progressWork.Node.Tag is MLTreeTag && (progressWork.Node.Tag as MLTreeTag).Syntetic))
                {
                    MLObjectTreeExpand objectTreeExpand = new MLObjectTreeExpand();

                    if (progressWork.Node.Tag is MLTreeTag && (progressWork.Node.Tag as MLTreeTag).PersistedObjectRole)
                    {
                        treeNodeCollection[progressWork.BackgroundWorkerNumber] = objectTreeExpand.UpdatePersistedChildObjects(
                                                                                    (progressWork.Node.Tag as MLTreeTag).ObjInstance,
                                                                                     progressWork.Node);
                    }
                    else
                    {
                        treeNodeCollection[progressWork.BackgroundWorkerNumber] = objectTreeExpand.UpdateNodeMembers(
                                                                                    (progressWork.Node.Tag as MLTreeTag).ObjInstance,
                                                                                     progressWork.Node.Nodes);
                    }

                    //Rendering
                    backgroundWorker[progressWork.BackgroundWorkerNumber].ReportProgress(0, new MLDoWork(WorkState.end, progressWork.Node, null, progressWork.BackgroundWorkerNumber));
                }
            }
        }

        /// <summary>
        /// Add root node(s)
        /// </summary>
        /// <param name="progressWork">Work to do</param>
        private void ProcessRoot(MLDoWork progressWork)
        {
            if (objectBrowser.BrowsedObjects != null)
            {
                foreach (object o in objectBrowser.BrowsedObjects)
                {
                    if (o != null)
                    {
                        backgroundWorker[progressWork.BackgroundWorkerNumber].ReportProgress(0, new MLDoWork(WorkState.start, MLObjectTreeExpand.GetRootNode(o), null, progressWork.BackgroundWorkerNumber));
                    }
                }
            }
        }

        /// <summary>
        /// Add changes to object tree
        /// </summary>
        /// <param name="progressWork">Work to do</param>
        private void BuildTree(MLDoWork progressWork)
        {
            objectBrowser.objectTree.BeginUpdate();

            if (progressWork.WorkState == WorkState.start)
            {
                objectBrowser.objectTree.Nodes.Add(progressWork.Node);
                progressWork.Node.Nodes.Add("Loading...");

                //Work around to fix .Net 2.0 bug
                if (progressWork.Node.NodeFont == null || progressWork.Node.NodeFont.Bold)
                    progressWork.Node.Text += String.Empty;

                objectBrowser.IsRunning = false;
            }
            else if (progressWork.WorkState == WorkState.end)
            {
                WorkExpandEnd(progressWork.Node, progressWork.BackgroundWorkerNumber);
            }
            else if (progressWork.WorkState == WorkState.aborted)
            {
                WorkAborted(progressWork.Node, progressWork.BackgroundWorkerNumber);
            }

            objectBrowser.objectTree.EndUpdate();
        }

        /// <summary>
        /// The end of expanding tree
        /// </summary>
        /// <param name="node">Expanded tree node</param>
        /// <param name="BackgroundWorkerNumber">Number of background worker</param>
        private void WorkExpandEnd(TreeNode node, int BackgroundWorkerNumber)
        {
            node.Nodes.Clear();

            foreach (MLNodeInfo nodeInfo in treeNodeCollection[BackgroundWorkerNumber])
            {
                nodeInfo.ParentNode.Add(nodeInfo.Node);
            }
            
            treeNodeCollection[BackgroundWorkerNumber].Clear();

            objectBrowser.IsRunning = false;

            foreach (BackgroundWorker bw in backgroundWorker)
            {
                if (bw.IsBusy && bw != backgroundWorker[BackgroundWorkerNumber])
                {
                    objectBrowser.IsRunning = true;
                    break;
                }
            }

            if (!objectBrowser.IsRunning)
            {
                foreach (Form form in objectBrowser.MainForm.MdiChildren)
                {
                    if ((form is MLObjectBrowser) && (form as MLObjectBrowser).IsRunning)
                    {
                        return;
                    }
                }
                
                objectBrowser.MainForm.Abort.Enabled = false;
                objectBrowser.MainForm.abortToolStripMenuItem.Enabled = false;
            }
        }

        /// <summary>
        /// Work is aborted
        /// </summary>
        /// <param name="node">Expanded tree node</param>
        /// <param name="BackgroundWorkerNumber">Number of background worker</param>
        private void WorkAborted(TreeNode node, int BackgroundWorkerNumber)
        {
            node.Nodes.Clear();
            node.Nodes.Add("Aborted");
            
            treeNodeCollection[BackgroundWorkerNumber].Clear();
            objectBrowser.IsRunning = false;
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="objectBrowser">Parent object browser</param>
        public MLAsyncWorker(MLObjectBrowser objectBrowser)
        {
            this.objectBrowser = objectBrowser;
            this.mThread = new Thread[numberOfBackgroundWorkers];
            this.backgroundWorker = new BackgroundWorker[numberOfBackgroundWorkers];
            this.treeNodeCollection = new List<MLNodeInfo>[numberOfBackgroundWorkers];
            
            for (int i = 0; i < numberOfBackgroundWorkers; i++)
            {
                if (backgroundWorker[i] == null)
                {
                    backgroundWorker[i] = new BackgroundWorker();
                }
                backgroundWorker[i].WorkerReportsProgress = true;
                backgroundWorker[i].WorkerSupportsCancellation = true;
                backgroundWorker[i].DoWork += new DoWorkEventHandler(WorkForBackgroundWorkers);
                backgroundWorker[i].ProgressChanged += new ProgressChangedEventHandler(ReportProgressOfBackgroundWorker);
                
                if (treeNodeCollection[i] == null)
                {
                    treeNodeCollection[i] = new List<MLNodeInfo>();
                }
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Dispose all threads and background workers
        /// </summary>
        public void Dispose()
        {
            foreach (Thread thread in mThread)
            {
                if (thread != null)
                    thread.Abort();
            }

            for (int i = 0; i < numberOfBackgroundWorkers; i++)
            {
                backgroundWorker[i].Dispose();
                treeNodeCollection[i].Clear();
            }
        }

        #endregion
    }
}
